package core.proto.annotation;

import static app.constants.CollabVizConstants.*;

import java.awt.Color;
import java.awt.Point;
import java.awt.Rectangle;
import java.awt.event.FocusAdapter;
import java.awt.event.FocusEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.util.LinkedList;
import java.util.List;

import javax.swing.BorderFactory;
import javax.swing.JInternalFrame;
import javax.swing.JLabel;
import javax.swing.JLayeredPane;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.SwingUtilities;

import core.proto.Util;
import core.proto.rightwindow.BoardPane;
import core.proto.rightwindow.LeftRightCommunicator;

public class AnnotationUI implements AnnotationEventSource {

	protected final Color LABEL_COLOR = NOTE_COLOR;
	protected final Color LABEL_COLOR_ALT = new Color(0xc3d9ff);
	protected final Color EDITOR_BACKGROUND = Color.white;
	protected final int LABEL_W = 100;
	protected final int LABEL_H = 30;
	protected final int NUM_CHAR_TO_SHOW = 10;

	protected JLabel label;				// nullable (when the UI sublcass doesn't have to display the text when not editing, such as in the timeline)
	protected JTextArea textArea;		
	protected JInternalFrame frame;
	protected AnnotationModel model;

	protected List<AnnotationRemovalListener> removalListeners; 
	protected List<AnnotationFocusListener> focusListeners;

	protected JLayeredPane parent;

	protected AnnotationUI (AnnotationModel model) throws CannotAddAnnotationException {
		// Check if right window's whiteboard lets us add a new annotation. 
		if (! BoardPane.getInstance().canCreateLocalPostIt()) {
			throw new CannotAddAnnotationException();
		}
		
		this.model = model;


		focusListeners = new LinkedList<AnnotationFocusListener>();
		removalListeners = new LinkedList<AnnotationRemovalListener>();

		textArea = new JTextArea();
		textArea.setLineWrap(true);
		textArea.setText(model.getUserText());
		textArea.setFocusable(true);
		textArea.setBackground(EDITOR_BACKGROUND);
		textArea.setBorder(BorderFactory.createLineBorder(Color.black));
		textArea.setWrapStyleWord(true);

		frame = new JInternalFrame("", false, false, false, false); // title, resizable, closable, maximizable, iconifiable (minimizable)


		// Scroll
		JScrollPane scroll = new JScrollPane(textArea);
		frame.add(scroll);
		frame.setBounds(new Rectangle(0, 0,ANNOTATION_POPUP_SIDE,ANNOTATION_POPUP_SIDE));
		frame.setSize(ANNOTATION_POPUP_SIDE,ANNOTATION_POPUP_SIDE);

		setupFocusListener();
	}

	public String getUserText() {
		return model.getUserText();
	}

	public void addMouseListener (MouseAdapter a) {
		if (this.label != null) {
			label.addMouseListener(a);
		}
	}

	private void setupFocusListener() {
		final AnnotationUI aui = this;
		FocusAdapter adapter = new FocusAdapter() {
			// Shrink when text changed. 
			public void focusLost(FocusEvent fe) {
				for(int i = focusListeners.size()-1; i>=0; --i){
					focusListeners.get(i).lostFocus();
				}

				model.setUserText(textArea.getText());
				// First check if user has deleted this annotation. 
				if (Annotation.shouldDeleteAnnotationWith(textArea.getText())){
					deleteAnnotation ();
				} else {
					doFocusLostModelViewUpdates();
				}
				
				LeftRightCommunicator.annotationUpdated(aui);

				// The code to save annotation to disk each time text is altered.
				// It takes care of removing annotations that are deleted. 
				AnnotationManager.writeFile(model.getDocKey());
			}

			public void focusGained(FocusEvent fe) {
				for(int i = focusListeners.size()-1; i>=0; --i){
					focusListeners.get(i).gainedFocus();
				}
				textArea.setEditable(true);
			}
		};

		textArea.addFocusListener(adapter);
	}
	
	public void deleteAnnotation () {
		removeFromPane();
		model.setDelete(true);
		// Notify listeners that we have removed the annotation. 
		for(int i = removalListeners.size()-1; i>=0; --i){
			removalListeners.get(i).annotationRemoved(this);
		}
	}

	/**
	 * Can call
	 * @param text
	 * @return True if annotation was deleted. False otherwise. 
	 */
	public boolean externalSetText (String text) {
		if (Annotation.shouldDeleteAnnotationWith(text)) {
			deleteAnnotation ();
			return true;
		} else {
			model.setUserText(text);
			if (label != null) {
				label.setText(this.abbreviateLabelText(model.getUserText()));
				label.repaint();
			}
			textArea.setText(model.getUserText());
			textArea.repaint();
		}
		return false;
	}


	/**
	 * Subclasses should override this method to handler the behavior when 
	 * the popup editor goes away and additional changes to the view and/or model
	 * are needed.  
	 */
	protected void doFocusLostModelViewUpdates () {
	}

	public void hide () {
		// Override in subclasses
		frame.setVisible(false);
	}

	public void shrink(double x, double y) {
		// Override in subclasses
	}

	public void addToPane(JLayeredPane parent) {
		this.parent = parent;
		parent.add(frame, JLayeredPane.DRAG_LAYER);
	}

	public void removeFromPane() {
		parent.remove(frame);
	}

	public boolean isEditing () {
		return frame.isVisible();
	}

	public void show(double screenX, double screenY) {
		frame.setLocation((int) screenX, (int) screenY);
		frame.setVisible(true);

		// request focus only works after frame becomes visible.
		// so we use invokelater. 
		SwingUtilities.invokeLater(new Runnable(){
			public void run() {
				textArea.requestFocusInWindow();
			}
		});
	}

	public Point getTextLabelLocation () {
		if (label != null)
			return label.getLocation();
		return new Point(0,0);
	}

	public Point getEditorLocation () {
		return frame.getLocation();
	}

	public void addAnnotationRemovalListener(AnnotationRemovalListener l) {
		removalListeners.add(l);		
	}

	public void addAnnotationFocusListener(AnnotationFocusListener l) {
		focusListeners.add(l);
	}

	protected String abbreviateLabelText (String txt) {
		return txt.length() < NUM_CHAR_TO_SHOW ? txt : txt.substring(0, NUM_CHAR_TO_SHOW);
	}
}
